( function () {
class OBJExporter {
  parse(object) {
    let output = '';
    let indexVertex = 0;
    let indexVertexUvs = 0;
    let indexNormals = 0;
    const vertex = new THREE.Vector3();
    const color = new THREE.Color();
    const normal = new THREE.Vector3();
    const uv = new THREE.Vector2();
    const face = [];

    function parseMesh(mesh) {
      let nbVertex = 0;
      let nbNormals = 0;
      let nbVertexUvs = 0;
      const geometry = mesh.geometry;
      const normalMatrixWorld = new THREE.Matrix3();

      if (geometry.isBufferGeometry !== true) {
        throw new Error('THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.');
      } // shortcuts


      const vertices = geometry.getAttribute('position');
      const normals = geometry.getAttribute('normal');
      const uvs = geometry.getAttribute('uv');
      const indices = geometry.getIndex(); // name of the mesh object

      output += 'o ' + mesh.name + '\n'; // name of the mesh material

      if (mesh.material && mesh.material.name) {
        output += 'usemtl ' + mesh.material.name + '\n';
      } // vertices


      if (vertices !== undefined) {
        for (let i = 0, l = vertices.count; i < l; i++, nbVertex++) {
          vertex.x = vertices.getX(i);
          vertex.y = vertices.getY(i);
          vertex.z = vertices.getZ(i); // transform the vertex to world space

          vertex.applyMatrix4(mesh.matrixWorld); // transform the vertex to export format

          output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
        }
      } // uvs


      if (uvs !== undefined) {
        for (let i = 0, l = uvs.count; i < l; i++, nbVertexUvs++) {
          uv.x = uvs.getX(i);
          uv.y = uvs.getY(i); // transform the uv to export format

          output += 'vt ' + uv.x + ' ' + uv.y + '\n';
        }
      } // normals


      if (normals !== undefined) {
        normalMatrixWorld.getNormalMatrix(mesh.matrixWorld);

        for (let i = 0, l = normals.count; i < l; i++, nbNormals++) {
          normal.x = normals.getX(i);
          normal.y = normals.getY(i);
          normal.z = normals.getZ(i); // transform the normal to world space

          normal.applyMatrix3(normalMatrixWorld).normalize(); // transform the normal to export format

          output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
        }
      } // faces


      if (indices !== null) {
        for (let i = 0, l = indices.count; i < l; i += 3) {
          for (let m = 0; m < 3; m++) {
            const j = indices.getX(i + m) + 1;
            face[m] = indexVertex + j + (normals || uvs ? '/' + (uvs ? indexVertexUvs + j : '') + (normals ? '/' + (indexNormals + j) : '') : '');
          } // transform the face to export format


          output += 'f ' + face.join(' ') + '\n';
        }
      } else {
        for (let i = 0, l = vertices.count; i < l; i += 3) {
          for (let m = 0; m < 3; m++) {
            const j = i + m + 1;
            face[m] = indexVertex + j + (normals || uvs ? '/' + (uvs ? indexVertexUvs + j : '') + (normals ? '/' + (indexNormals + j) : '') : '');
          } // transform the face to export format


          output += 'f ' + face.join(' ') + '\n';
        }
      } // update index


      indexVertex += nbVertex;
      indexVertexUvs += nbVertexUvs;
      indexNormals += nbNormals;
    }

    function parseLine(line) {
      let nbVertex = 0;
      const geometry = line.geometry;
      const type = line.type;

      if (geometry.isBufferGeometry !== true) {
        throw new Error('THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.');
      } // shortcuts


      const vertices = geometry.getAttribute('position'); // name of the line object

      output += 'o ' + line.name + '\n';

      if (vertices !== undefined) {
        for (let i = 0, l = vertices.count; i < l; i++, nbVertex++) {
          vertex.x = vertices.getX(i);
          vertex.y = vertices.getY(i);
          vertex.z = vertices.getZ(i); // transform the vertex to world space

          vertex.applyMatrix4(line.matrixWorld); // transform the vertex to export format

          output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
        }
      }

      if (type === 'Line') {
        output += 'l ';

        for (let j = 1, l = vertices.count; j <= l; j++) {
          output += indexVertex + j + ' ';
        }

        output += '\n';
      }

      if (type === 'LineSegments') {
        for (let j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1) {
          output += 'l ' + (indexVertex + j) + ' ' + (indexVertex + k) + '\n';
        }
      } // update index


      indexVertex += nbVertex;
    }

    function parsePoints(points) {
      let nbVertex = 0;
      const geometry = points.geometry;

      if (geometry.isBufferGeometry !== true) {
        throw new Error('THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.');
      }

      const vertices = geometry.getAttribute('position');
      const colors = geometry.getAttribute('color');
      output += 'o ' + points.name + '\n';

      if (vertices !== undefined) {
        for (let i = 0, l = vertices.count; i < l; i++, nbVertex++) {
          vertex.fromBufferAttribute(vertices, i);
          vertex.applyMatrix4(points.matrixWorld);
          output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z;

          if (colors !== undefined) {
            color.fromBufferAttribute(colors, i);
            output += ' ' + color.r + ' ' + color.g + ' ' + color.b;
          }

          output += '\n';
        }
      }

      output += 'p ';

      for (let j = 1, l = vertices.count; j <= l; j++) {
        output += indexVertex + j + ' ';
      }

      output += '\n'; // update index

      indexVertex += nbVertex;
    }

    object.traverse(function (child) {
      if (child.isMesh === true) {
        parseMesh(child);
      }

      if (child.isLine === true) {
        parseLine(child);
      }

      if (child.isPoints === true) {
        parsePoints(child);
      }
    });
    return output;
  }

}

THREE.OBJExporter = OBJExporter;
} )();
